package com.cnb.servlet.v1;

import com.cnb.servlet.v1.annotation.*;
import com.cnb.servlet.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class CNBServlet extends HttpServlet {

    private Logger log = LoggerFactory.getLogger(CNBServlet.class);

    private Properties configProperties = new Properties();

    private List<String> classNames = new ArrayList<String>();

    private Map<String,Object> ioc = new HashMap<String,Object>();

    private Map<String,Method> pathMethod = new HashMap<String,Method>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Content-type", "text/html;charset=UTF-8");
        resp.setCharacterEncoding("UTF-8");
        try{
            doDispatch(req,resp);
        }catch (Exception e){
            resp.getWriter().write(e.getMessage());
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException {
        String requestURI = req.getRequestURI().replaceAll("/+","/");
        if(!this.pathMethod.containsKey(requestURI)){
            resp.getWriter().write("路径["+requestURI+"]没有找到!");
            return;
        }
        Method method = this.pathMethod.get(requestURI);


        Map<String,Integer> paramIndexMapping = new HashMap<String, Integer>();

        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                if(annotation instanceof RequestParam){
                    String paramName = ((RequestParam) annotation).value();
                    if(!"".equals(paramName.trim())){
                        paramIndexMapping.put(paramName,i);
                    }
                }
            }
        }

        Class<?> [] paramTypes = method.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
            Class<?> type = paramTypes[i];
            if(type == HttpServletRequest.class || type == HttpServletResponse.class){
                paramIndexMapping.put(type.getName(),i);
            }
        }

        Object[] paramValues = new Object[paramTypes.length];

        Map<String,String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> param : params.entrySet()) {
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]","")
                    .replaceAll("\\s","");

            if(!paramIndexMapping.containsKey(param.getKey())){continue;}

            int index = paramIndexMapping.get(param.getKey());

            //涉及到类型强制转换
            paramValues[index] = value;
        }

        if(paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
            int index = paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }

        if(paramIndexMapping.containsKey(HttpServletResponse.class.getName())){
            int index = paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }


        String beanName = StrUtil.toLowerCaseFirst(method.getDeclaringClass().getSimpleName());
        method.invoke(ioc.get(beanName),paramValues);
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        doScanner(configProperties.getProperty("scanPackage"));

        doInstance();

        doAutowire();

        doInitHandlerMapping();

        log.info("框架初始化完成(*∩_∩*)~");
    }

    /**
     * 初始HandlerMapping MVC功能
     */
    private void doInitHandlerMapping() {
        if(this.ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : this.ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();

            //只有当该类上加入了Controller注解后才会初始化方法和接口名的对应
            if(!aClass.isAnnotationPresent(Controller.class)){
                continue;
            }

            String beasUrl = "";
            if(aClass.isAnnotationPresent(RequestMapping.class)){
                beasUrl = aClass.getAnnotation(RequestMapping.class).value();
            }

            Method[] publicMethods = aClass.getMethods();
            for (Method publicMethod : publicMethods) {
                if(!publicMethod.isAnnotationPresent(RequestMapping.class)){
                    continue;
                }
                RequestMapping requestMappingAnnotation = publicMethod.getAnnotation(RequestMapping.class);
                String url = requestMappingAnnotation.value();
                String allUrl =  ("/" + beasUrl + "/" + url).replaceAll("/+","/");
                pathMethod.put(allUrl,publicMethod);
            }
        }
    }

    /**
     * 进行依赖注入
     */
    private void doAutowire() {
        if(ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //遍历该对象里面所有的字段
            for (Field field : entry.getValue().getClass().getDeclaredFields()) {
                //判断该字段有没有加Autowrite注解,如果没有就遍历下一个字段
                if(!field.isAnnotationPresent(Autowrite.class)){
                    continue;
                }
                Autowrite autowriteAnnotation = field.getAnnotation(Autowrite.class);
                String beanName = "";
                //如果这里的field是一个接口,那么这里beanName将是一个该接口的全限定名
                if(field.getType().isInterface()){
                    beanName = field.getType().getName();
                }else{
                    if(!StrUtil.isBlank(autowriteAnnotation.value())){
                        beanName = autowriteAnnotation.value().trim();
                    }else{
                        //这里都会将该字段的变量名首字母转成小写
                        beanName = StrUtil.toLowerCaseFirst(field.getName());
                    }
                }
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 初始化IOC容器，将扫描到的类进行实例化
     * 将实例化后的对象存入Ioc里面，它是一个HashMap类型的数据
     */
    private void doInstance() {
        //如果当前集合里面没有元素，也就不用实例化对象，直接返回即可
        if(this.classNames.isEmpty()){return;}
        try{
            for (String className : this.classNames) {
                Class<?> aClass = Class.forName(className);
                String beanName = StrUtil.toLowerCaseFirst(aClass.getSimpleName());
                if(aClass.isAnnotationPresent(Controller.class)){
                    Controller controllerAnnotation = aClass.getAnnotation(Controller.class);
                    if(!StrUtil.isBlank(controllerAnnotation.alias())){
                        beanName = StrUtil.toLowerCaseFirst(controllerAnnotation.alias());
                    }

                    Object o = aClass.getDeclaredConstructor().newInstance();
                    if(ioc.containsKey(beanName)){
                        throw new RuntimeException("[" + beanName + "]已存在，请修改代码或者使用别名的方式");
                    }
                    ioc.put(beanName,o);
                }else if(aClass.isAnnotationPresent(Service.class)){
                    Service serviceAnnotation = aClass.getAnnotation(Service.class);
                    if(!StrUtil.isBlank(serviceAnnotation.alias())){
                        beanName = serviceAnnotation.alias();
                    }
                    Object o = aClass.getDeclaredConstructor().newInstance();
                    ioc.put(beanName,o);
                    for (Class<?> anInterface : aClass.getInterfaces()) {
                        if(ioc.containsKey(anInterface.getName())){
                            throw new RuntimeException("[" + beanName + "]已存在，请修改代码或者使用别名的方式");
                        }
                        ioc.put(anInterface.getName(),o);
                    }
                }else{
                    continue;
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 扫描包
     * @param scanPackage 需要扫描的包
     * @deprecated 扫描该下面的所有class文件，将其全限定类名加入到集合里面
     */
    private void doScanner(String scanPackage) {
        URL resourcePath = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File scanPath = new File(resourcePath.getPath());
        for (File itemFile : scanPath.listFiles()) {
            if(itemFile.isDirectory()){
                doScanner(scanPackage + "." + itemFile.getName());
            }else{
                if(!itemFile.getName().endsWith(".class")){continue;}
                String classAllName = scanPackage + "." + itemFile.getName().replaceAll(".class","");
                classNames.add(classAllName);
            }
        }
    }

    /**
     * 加载配置文件
     * @param contextConfigLocation 配置文件所在的路径
     */
    private void doLoadConfig(String contextConfigLocation) {

        try(InputStream stream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);){
            configProperties.load(stream);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
